
#include "UTL2Cache.hpp"
#include "openvip_third/xs_coupledl2.h"
#include "openvip/mem.h"
#include <map>

//  make RUN=tb_coupledl2_cache SIMULATOR=vcs WAVE_FORMAT=FSDB TRACE=ON

using namespace ovip;

void test_xs_coupuledl2_fullsys(int c, char **v)
{
    UTL2Cache dut({"dut/UT_coupledl2_cache_trace/libDPIL2Cache.so",
                   "+vcs+initreg+random"});
    std::function<int(bool)> stepfunc = [&dut](bool d) {
        dut.step(d);
        return 0;
    };

    XClock clk(stepfunc, {&dut.clock}, {&dut.port});
    dut.reset = 1;
    for (int i = 0; i < 1000; i++) { clk.Step(); }
    dut.reset = 0;
    clk.Step(2000);
    Debug("reset cache complete!");

    bool print_dcache_trace = false;

    XSCoupledL2Unit core_0_dcache(1, "core0_d", dut.port, clk,
                                      "master_port_0_0_", true, print_dcache_trace);
    XSCoupledL2Unit core_0_icache(2, "core0_i", dut.port, clk,
                                      "master_port_1_0_", false);
    XSCoupledL2Unit core_1_dcache(3, "core1_d", dut.port, clk,
                                      "master_port_2_0_", true, print_dcache_trace);
    XSCoupledL2Unit core_1_icache(4, "core1_i", dut.port, clk,
                                      "master_port_3_0_", false);

    SimpleMemory goldn("golden", 4, 1024, 0xff);

    // data R/W
    u_int64_t d0_addr;
    u_int8_t d0_data;
    bool d0_cmp;
    bool d0_success;
    bool d0_busy = false;
    bool d0_read;

    u_int64_t d1_addr;
    u_int8_t d1_data;
    bool d1_cmp;
    bool d1_success;
    bool d1_busy = false;
    bool d1_read;

    // idata R
    u_int64_t i0_addr;
    u_int8_t i0_data;
    bool i0_cmp;
    bool i0_busy = false;
    bool i0_success;

    u_int64_t i1_addr;
    u_int8_t i1_data;
    bool i1_cmp;
    bool i1_busy = false;
    bool i1_success;

    // count
    u_int64_t lstc = clk.clk;
    u_int64_t ltim = uTime();
    int c_success  = 0;
    int c_fail     = 0;
    
    // force compare
    auto f_cmp = false;
    u_int64_t min_address = 1 << 30;
    u_int64_t max_address = min_address + 4 * (1 << 10);

    u_int8_t default_mm_value = 0xff;
    std::map<u_int64_t, u_int8_t> trace;
    bool enable_trace = false;

    Debug("address range = 0x%lx ~ 0x%lx (%s)", min_address, max_address, FmtSize(max_address - min_address).c_str())
    //XSeed(0);
    while (true) {
        auto seed = rand();
        XSeed(seed);
        FOR_COUNT(1000000)
        {
            // d0
            if (!d0_busy) {
                d0_busy    = true;
                d0_cmp     = false;
                d0_success = false;
                d0_addr    = xRandom(min_address, max_address);
                if (xRandom(0, 2) == 0) {
                    d0_read = true;
                    core_0_dcache.CpuLoad(d0_addr, &d0_data, &d0_cmp,
                                          &d0_success);
                } else {
                    d0_read = false;
                    d0_data = xRandom(0, 255);
                    core_0_dcache.CpuStore(d0_addr, d0_data, &d0_cmp,
                                           &d0_success);
                }
            }
            // d1
            if (!d1_busy) {
                d1_busy    = true;
                d1_cmp     = false;
                d1_success = false;
                d1_addr    = xRandom(min_address, max_address);
                if (xRandom(0, 2) == 0) {
                    d1_read = true;
                    core_1_dcache.CpuLoad(d1_addr, &d1_data, &d1_cmp,
                                          &d1_success);
                } else {
                    d1_read = false;
                    d1_data = xRandom(0, 255);
                    core_1_dcache.CpuStore(d1_addr, d1_data, &d1_cmp,
                                           &d1_success);
                }
            }

            auto block_0 = d0_addr - d0_addr%64;
            auto block_1 = d1_addr - d1_addr%64;
            if((block_0 == block_1) && d0_success && d1_success && (!d0_read || !d1_read)){
                Assert(false, "[%ld] Two dcache operate(0:%s, 1:%s) the same block addr[0x%lx]", clk.clk, d0_read?"R":"W", d1_read?"R":"W", block_0);
            }

            // check 0
            if (d0_busy && d0_cmp && core_0_dcache.OK()) {
                if (d0_success || f_cmp) {
                    if (d0_read) {
                        auto m_data = (u_int8_t)goldn.Read(d0_addr, 1);
                        auto t_data = default_mm_value;
                        if(enable_trace){
                            if(trace.count(d0_addr))t_data = trace[d0_addr];
                        }
                        if (m_data != d0_data)
                            Assert(false, "[%ld] [d0] Read: %lx [blk:%lx] compare fail: M(%02x) != C(%02x), (T: %02x) Seed=%d",
                                 clk.clk, d0_addr, block_0, m_data, d0_data, t_data, seed);
                    } else {
                        goldn.Write(d0_addr, 1, d0_data);
                        if(enable_trace)trace[d0_addr] = d0_data;
                    }
                }
                c_fail += int(!d0_success);
                c_success += int(d0_success);
                d0_busy = false;
                d0_cmp = false;
                d0_success = false;
            }

            // check 1
            if (d1_busy && d1_cmp && core_1_dcache.OK()) {
                if (d1_success || f_cmp) {
                    if (d1_read) {
                        auto m_data = (u_int8_t)goldn.Read(d1_addr, 1);
                        auto  t_data = default_mm_value;
                        if(enable_trace){
                            if(trace.count(d1_addr))t_data = trace[d1_addr];
                        }
                        if (m_data != d1_data)
                            Assert(false, "[%ld] [d1] Read: %lx [blk:%lx] compare fail: M(%02x) != C(%02x), (T: %02x) Seed=%d",
                                 clk.clk, d1_addr, block_1, m_data, d1_data, t_data, seed);
                    } else {
                        goldn.Write(d1_addr, 1, d1_data);
                        if(enable_trace)trace[d1_addr] = d1_data;
                    }
                }
                c_fail += int(!d1_success);
                c_success += int(d1_success);
                d1_busy = false;
                d1_cmp = false;
                d1_success = false;
            }

            // ICahe read
            if(!i0_busy && core_0_icache.OK()){
                if(xRandom(0, 2) == 0){
                    i0_busy = true;
                    i0_addr = xRandom(min_address, max_address);
                    core_0_icache.CpuGet(i0_addr, &i0_data, &i0_cmp, &i0_success);
                }
            }
            if(!i1_busy && core_1_icache.OK()){
                if(xRandom(0, 2) == 0){
                    i1_busy = true;
                    i1_addr = xRandom(min_address, max_address);
                    core_1_icache.CpuGet(i1_addr, &i1_data, &i1_cmp, &i1_success);
                }
            }

            if(i0_success){
                auto block_0 = i0_addr - i0_addr%64;
                auto m_data = (u_int8_t)goldn.Read(i0_addr, 1);
                if (m_data != i0_data)Assert(false, "[%ld] [d0] Read: %lx [blk:%lx] compare fail: M(%02x) != C(%02x), Seed=%d", clk.clk, i0_addr, block_0, m_data, i0_data, seed);
                i0_busy = false;
            }

            if(i1_success){
                auto block_1 = i1_addr - i1_addr%64;
                auto m_data = (u_int8_t)goldn.Read(i1_addr, 1);
                if (m_data != i1_data)Assert(false, "[%ld] [d0] Read: %lx [blk:%lx] compare fail: M(%02x) != C(%02x), Seed=%d", clk.clk, i1_addr, block_1, m_data, i1_data, seed);
                i1_busy = false;
            }

            if (clk.clk % 10000 == 0) {
                auto deta_c = clk.clk - lstc;
                auto deta_t = (uTime() - ltim) / 1000.0;
                Debug("[%ld] success = %d fail = %d, speed: %.2f c/s           d0_cmp:%d, d0_ok:%d, d0_busy:%d, d1_cmp:%d, d1_ok:%d, d1_busy:%d", clk.clk,
                      c_success, c_fail, 1000 * float(deta_c) / float(deta_t), d0_cmp, core_0_dcache.OK(), d0_busy, d1_cmp, core_1_dcache.OK(), d1_busy);
            }

            // END
            lstc = clk.clk;
            ltim = uTime();
            clk.Step();
        }
    }
    Debug("Test complete!");
}

int main(int c, char **v)
{
    Debug("test: test_xs_coupuledl2_fullsys");
    test_xs_coupuledl2_fullsys(c, v);
}
